package com.github.base.permission;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
import com.github.base.domain.Dept;
import com.github.base.enums.DataScopeEnum;
import com.github.base.service.DeptService;
import com.github.base.service.RoleDeptService;
import com.github.base.service.UserRoleService;
import com.github.base.service.UserService;
import com.github.base.vo.user.UserInfoVO;
import com.github.base.vo.user.UserRoleVO;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;

/**
 * 自定义数据权限处理器
 *
 * @author zhanghui
 * @date 2023/10/18 10:34
 */

@Slf4j
public class CustomDataPermissionHandler implements MultiDataPermissionHandler {

    /**
     * 拦截selectById、getById和mapper层添加自定义注解的方法
     * 目前处理mapper层 selectById 和 自己写sql的方法拦截 service层使用getById也是使用了selectById
     */
//    private static final List<String> methodList = new ArrayList<>();
    private static final ExpressionParser PARSER = new SpelExpressionParser();
    private BeanResolver beanResolver;

//    static {
//        methodList.add("selectById");
//    }
    @Override
    @SneakyThrows
    public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
        // xxxx.xxx.xxx.selectById
        String classPath = mappedStatementId.substring(0, mappedStatementId.lastIndexOf(StrUtil.DOT));
        String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StrUtil.DOT) + 1);
        String tableName = table.getName();
        String aliasName = "";
        Alias alias = table.getAlias();
        if (alias != null) {
            aliasName = alias.getName();
        }
//        if (CollUtil.contains(methodList, methodName)) {
//            log.info("数据权限方法名拦截");
//            // 这里获取当前登录人token信息 为空忽略 为一级平台忽略 二级租户、三级应用、四级商户添加
//            Integer accountGrade = SecurityUtil.getAccountGrade();
//            if (accountGrade == null || GradeEnum.SU_ADMIN.getCode().equals(accountGrade)) {
//                log.info("当前人为空/级别为空/saas层级");
//                return null;
//            }
//            String permissionSql = this.getPermissionSql(aliasName, null);
//            Expression expression = CCJSqlParserUtil.parseCondExpression(permissionSql);
//            return new LeftAndExpression(expression);
//        }

        // 这里处理方法名不是selectById 自己写的sql并加@DataPermission注解的
        Class<?> aClass = Class.forName(classPath);
        Method[] methods = aClass.getMethods();

        for (Method method : methods) {
            DataPermission annotation = method.getAnnotation(DataPermission.class);
            if (methodName.equals(method.getName()) && ObjectUtil.isNotNull(annotation)) {
                String annoPermissionSql = this.getAnnoPermissionSql(tableName, aliasName, annotation, method);
                if (StrUtil.isBlank(annoPermissionSql)) {
                    log.info("多表操作 继续循环");
                } else {
                    Expression expression = CCJSqlParserUtil.parseCondExpression(annoPermissionSql);
                    return new LeftAndExpression(expression);
                }
            }
        }
        for (Method method : methods) {
            DataScope annotation = method.getAnnotation(DataScope.class);
            if (methodName.equals(method.getName()) && ObjectUtil.isNotNull(annotation)) {
                DataPermission[] value = annotation.value();
                if (value.length == 1) {
                    DataPermission dataPermission = value[0];
                    String annoPermissionSql = this.getAnnoPermissionSql(tableName, aliasName, dataPermission, method);
                    if (StrUtil.isBlank(annoPermissionSql)) {
                        log.info("多表操作 继续循环");
                    } else {
                        Expression expression = CCJSqlParserUtil.parseCondExpression(annoPermissionSql);
                        return new LeftAndExpression(expression);
                    }
                } else {
                    Expression expression = null;
                    for (int i = 0; i < value.length; i++) {
                        DataPermission dataPermission = value[i];
                        String annoPermissionSql = this.getAnnoPermissionSql(tableName, aliasName, dataPermission, method);
                        if (StrUtil.isBlank(annoPermissionSql)) {
                            log.info("多表操作 继续循环");
                        } else {
                            Expression tempEexpression = CCJSqlParserUtil.parseCondExpression(annoPermissionSql);
                            if (tempEexpression == null) {
                                continue;
                            }
                            if (i == 0) {
                                expression = new LeftAndExpression(tempEexpression);
                            } else {
                                if (expression != null) {
                                    expression = new AndExpression(expression, tempEexpression);
                                } else {
                                    expression = new LeftAndExpression(tempEexpression);
                                }

                                if (i == value.length - 1) {
                                    return expression;
                                }
                            }

                        }
                    }
                }

            }
        }

        return null;
    }

    public String getAnnoPermissionSql(String tableName, String aliasName, DataPermission annotation, Method method) {
        String sql = "";
        String annoTableName = annotation.tableName();
        String annoColumnName = annotation.columnName();
//        String annoColumnValue = annotation.columnValue();
        String annoColumnValue = "";
        // 这里处理只在mapper上打了注解 单表的拦截 与selectById一致
        if (StrUtil.isBlank(annoTableName) && StrUtil.isBlank(annoColumnName) && StrUtil.isBlank(annoColumnValue)) {
            sql = this.getPermissionSql(aliasName, null);
            return sql;
        }
        // 这里处理表名指定 多表操作 指定主表 列字段和表达式不指定
        if (StrUtil.isNotBlank(annoTableName) && StrUtil.isBlank(annoColumnName) && StrUtil.isBlank(annoColumnValue)) {
            if (annoTableName.equals(tableName)) {
                // 这里处理表名指定 但是没有给别名 用主表名覆盖别名
                if (StrUtil.isBlank(aliasName)) {
                    aliasName = annoTableName;
                }
                sql = this.getPermissionSql(aliasName, null);
            }
            return sql;
        }
        // 这里处理指定表名 指定字段 没有指定表达式
        if (StrUtil.isNotBlank(annoTableName) && StrUtil.isNotBlank(annoColumnName) && StrUtil.isBlank(annoColumnValue)) {
            if (annoTableName.equals(tableName)) {
                sql = this.getPermissionSql(aliasName, annoColumnName);
            }
            return sql;
        }
        // 指定表名 字段名 表达式
        if (StrUtil.isNotBlank(annoTableName) && StrUtil.isNotBlank(annoColumnName) && StrUtil.isNotBlank(annoColumnValue)) {
            if (annoTableName.equals(tableName)) {
                return this.getSpelSql(aliasName, annoColumnName, annoColumnValue, method);
            }
            return sql;
        }
        // 指定字段名 表达式 没指定表名
        if (StrUtil.isBlank(annoTableName) && StrUtil.isNotBlank(annoColumnName) && StrUtil.isNotBlank(annoColumnValue)) {
            return this.getSpelSql(aliasName, annoColumnName, annoColumnValue, method);
        }
        // 其他的不给处理
        log.info("其他注解传入参数未实现");
        return sql;

    }

    /**
     * 处理表达式拼接
     *
     * @param aliasName
     * @param annoColumnName
     * @param annoColumnValue
     * @param method
     * @return
     */
    private String getSpelSql(String aliasName, String annoColumnName, String annoColumnValue, Method method) {
        String sql = "";
        List<Long> parse = this.parse(annoColumnValue, method, method.getParameters());
        if (CollUtil.isEmpty(parse)) {
            return sql;
        }
        String customSql = " ";
        if (StrUtil.isNotBlank(aliasName)) {
            customSql = customSql + aliasName + StrUtil.DOT + annoColumnName;
        } else {
            customSql = customSql + annoColumnName;
        }
        StringJoiner stringJoiner = new StringJoiner(StrUtil.COMMA);
        for (Long s : parse) {
            stringJoiner.add(String.valueOf(s));
        }
        sql = customSql + " in (" + stringJoiner + ")";
        return sql;
    }


    /**
     * 处理selectById和没有表达式的sql拼接
     *
     * @param aliasName
     * @param annoColumnName
     * @return
     */
    public String getPermissionSql(String aliasName, String annoColumnName) {
        String sql = " ";
        if (StrUtil.isNotBlank(aliasName)) {
            sql = sql + aliasName + StrUtil.DOT;
        }
        if (StrUtil.isBlank(annoColumnName)) {
            annoColumnName = "dept_id";
        }
        long userId = StpUtil.getLoginIdAsLong();
        UserService userService = SpringUtil.getBean(UserService.class);
        UserInfoVO userInfoVO = userService.currentInfo();
        UserRoleService userRoleService = SpringUtil.getBean(UserRoleService.class);
        List<UserRoleVO> roleList = userRoleService.getUserRoleVoByUserId(userId);
        if (CollUtil.isEmpty(roleList)) {
            return sql + annoColumnName + " = " + "-1";
        } else {
            Set<Integer> dataScopeList = roleList.stream().map(UserRoleVO::getDataScope).collect(Collectors.toSet());
            if (CollUtil.contains(dataScopeList, DataScopeEnum.ALL.getValue())) {
                return "";
            } else {
                // 本部门及以下部门
                if (CollUtil.contains(dataScopeList, DataScopeEnum.SELF_DEPT_AND_CHILD.getValue())) {
                    Long deptId = userInfoVO.getDeptId();
                    DeptService deptService = SpringUtil.getBean(DeptService.class);
                    List<Dept> childById = deptService.findChildById(deptId);
                    Set<Long> collect = childById.stream().map(Dept::getId).collect(Collectors.toSet());
                    collect.add(deptId);
                    StringJoiner sj = new StringJoiner(StrUtil.COMMA);
                    for (Long s : collect) {
                        sj.add(String.valueOf(s));
                    }
                    return sql + annoColumnName + " in (" + sj + ")";
                } else if (CollUtil.contains(dataScopeList, DataScopeEnum.SELF_DEPT.getValue())) {
                    return sql + annoColumnName + " = " + userInfoVO.getDeptId();
                } else if (CollUtil.contains(dataScopeList, DataScopeEnum.CUSTOM.getValue())) {
                    // stream遍历角色列表 数据权限为自定义 收集角色id并且去重
                    List<Long> roleIds = roleList.stream().filter(item -> item.getDataScope().equals(DataScopeEnum.CUSTOM.getValue())).map(UserRoleVO::getId).collect(Collectors.toList());
                    // 查询出所有自定义角色的部门
                    RoleDeptService roleDeptService = SpringUtil.getBean(RoleDeptService.class);
                    Set<Long> ids = roleDeptService.findDeptIdsByRoleIds(roleIds);
                    StringJoiner sj = new StringJoiner(StrUtil.COMMA);
                    for (Long s : ids) {
                        sj.add(String.valueOf(s));
                    }
                    return sql + annoColumnName + " in (" + sj + ")";
                } else {
                    throw new RuntimeException("当前用户没有数据权限");
                }
            }
        }
    }


    /**
     * 解析spel
     *
     * @param spel
     * @param method
     * @param args
     * @return
     */
    public List<Long> parse(String spel, Method method, Object[] args) {
        // 获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);
        // spel上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        if (ObjectUtil.isNull(this.beanResolver)) {
            beanResolver = new BeanFactoryResolver(SpringUtil.getApplicationContext());
        }
        context.setBeanResolver(beanResolver);
        // 把方法参数放入spel上下文中
        if (paraNameArr != null) {
            for (int i = 0; i < paraNameArr.length; i++) {
                context.setVariable(paraNameArr[i], args[i]);
            }
        }
        return (List<Long>) PARSER.parseExpression(spel).getValue(context, List.class);
    }

}
